package com.axapoint.opoc.core.dao;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.criterion.Restrictions;
import org.joda.time.DateTime;
import org.joda.time.Interval;

import com.anasoft.os.daofusion.bitemporal.Bitemporal;
import com.anasoft.os.daofusion.bitemporal.TimeUtils;
import com.axapoint.opoc.core.IntervalException;
import com.axapoint.opoc.core.dao.ObjectRegistration._Bitemporal;
import com.axapoint.opoc.core.dao.ObjectRegistration._Value;
import com.axapoint.opoc.core.dao.ObjectRegistration._Value.Operation;
import com.axapoint.opoc.core.model.util.HibernateUtil;


public abstract class AbstractFacade<O extends ObjectRegistration,V,B> {
	
	private final Class<O> oClass;
	private final Class<V> vClass;
	private final String userId;
	private final String systemId;
	
	protected AbstractFacade(String systemId, String userId, O obj, V vObj) {
		oClass = (Class<O>)obj.getClass();
		vClass = (Class<V>)vObj.getClass();
		this.userId = userId;
		this.systemId = systemId;
	}
	
	public V getValue(String uuid) {
		return getValue(uuid, null);
	}

	public V getValue(String uuid, DateTime at) {
		O o = getEntity(uuid);
		if (o != null) {
			_Bitemporal bi;
			if (at != null) {
				bi = (_Bitemporal)o.getValues().get(at);
			} else {
				bi = (_Bitemporal)o.getValues().get();
			}
			if (bi == null) return null;
			return (V)bi.getValue();
		}
		
		return null;		
	}
	public O getEntity(String uuid) {
		O e = _getEntity(uuid);
		HibernateUtil.getCurrentSession().refresh(e);
		return e;
	}
	private O _getEntity(String uuid) {
		Criteria crit = HibernateUtil.getCurrentSession().createCriteria(oClass);
		
		crit.add(Restrictions.eq("uuid", uuid));
		List list = crit.list();
		
		if (list.size() == 1) {

			return (O)list.get(0);
		}
		return null;		
	}

	public O[] getAllEntities() {
		Criteria crit = HibernateUtil.getCurrentSession().createCriteria(oClass);
		List<O> list = crit.list();
		for (O e : list) {
			HibernateUtil.getCurrentSession().refresh(e);
		}
		
		return list.toArray((O[])Array.newInstance(oClass, 0));
	}
	
	public V[] getAllValues(DateTime at) {
		ArrayList<V> result = new ArrayList<V>();
		for (O o : getAllEntities()) {
			result.add(getValue(o.getUuid(), at));
		}
		return result.toArray((V[])Array.newInstance(vClass, 0));
	}

	public V[] findValues(String key, Object value) {
		Criteria crit = HibernateUtil.getCurrentSession().createCriteria(vClass);
		
		crit.add(Restrictions.eq(key, value));
		List<V> list = crit.list();
		for (V e : list) {
			HibernateUtil.getCurrentSession().refresh(e);
		}
		
		return list.toArray((V[])Array.newInstance(vClass, 0));
	}

	public O create(V value, String comment, Interval validityInterval) {
		O ot = null;
		try {
			ot = oClass.newInstance();
			HibernateUtil.getCurrentSession().save(ot);

			Constructor<V> c;
			c = vClass.getDeclaredConstructor(oClass, String.class, String.class, Operation.class, String.class, vClass);
			V v = c.newInstance(ot, getSystemId(), getUserId(), Operation.CREATE, comment, value);
			HibernateUtil.getCurrentSession().save(v);
			
			ot.getValues().set(v, validityInterval);
			HibernateUtil.getCurrentSession().save(ot);
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return ot;
	}

	public void update(String uuid, V value, String comment, Interval validityInterval) {
		O ot = null;
		try {
			ot = _getEntity(uuid);
			HibernateUtil.getCurrentSession().save(ot);

			Constructor<V> c;
			c = vClass.getDeclaredConstructor(oClass, String.class, String.class, Operation.class, String.class, vClass);
			V v = c.newInstance(ot, getSystemId(), getUserId(), Operation.EDIT, comment, value);
			HibernateUtil.getCurrentSession().save(v);

			ot.getValues().set(v, validityInterval);
			HibernateUtil.getCurrentSession().save(ot);
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}

	// Find all effected entities of change in this entity's validity interval 
	public abstract ObjectRegistration[] getAffectedEntities(String uuid, Interval newInterval);
	
	public List<B> getEvolution(String uuid, DateTime validOn) {
		O ot = getEntity(uuid);
		return ot.getValues().getEvolution(validOn);
	}
	
	public List<B> getHistory(String uuid, DateTime knownOn) {
		O ot = getEntity(uuid);

		return ot.getValues().getHistory(knownOn);
	}
	
	public String getDifference(String uuid, DateTime t0, DateTime t1) {
		O ot = getEntity(uuid);
		
		_Bitemporal bi;
		bi = (_Bitemporal)ot.getValues().get(t0);
		_Value valueT0 = bi.getValue();
		bi = (_Bitemporal)ot.getValues().get(t1);
		_Value valueT1 = bi.getValue();

		return valueT0.diff(valueT1, t0, t1);
	}

	// Add registration that ends valid time for all values
	public void forget(String uuid, DateTime fromTime) {
		O ot = getEntity(uuid);
		
		ot.getValues().end(fromTime);
		HibernateUtil.getCurrentSession().save(ot);
	}
	
	// Add registration with operation delete valid time: -oo <-> oo same values
	public void delete(String uuid, String comment) {
		deletePassivate(uuid, comment, Operation.DELETE);
	}

	// Add registration with operation passivated valid time: -oo <-> oo same values
	public void passivate(String uuid, String comment) {
		deletePassivate(uuid, comment, Operation.PASSIVATE);
	}
	
	private void deletePassivate(String uuid, String comment, Operation op) {
		O ot = null;
		try {
			ot = _getEntity(uuid);
			HibernateUtil.getCurrentSession().save(ot);

			Constructor<V> c;
			c = vClass.getDeclaredConstructor(oClass, String.class, String.class, Operation.class, String.class, vClass);
			V v = c.newInstance(ot, getSystemId(), getUserId(), op, comment, ((_Bitemporal)ot.getValues().get()).getValue());
			HibernateUtil.getCurrentSession().save(v);

			ot.getValues().set(v, new Interval(-TimeUtils.END_OF_TIME, TimeUtils.END_OF_TIME));
			HibernateUtil.getCurrentSession().save(ot);
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}
	
	public String getUserId() {
		return userId;
	}
	
	public String getSystemId() {
		return systemId;
	}
	
	protected void checkValidityInterval(Interval interval, ObjectRegistration ... objs) {
		
		ArrayList<ObjectRegistration> errors = new ArrayList<ObjectRegistration>();
		for (ObjectRegistration o : objs) {
			Bitemporal bi = o.getValues().get(interval.getStart());
			if (bi != null) {
				Interval vi = bi.getValidityInterval();
				if (!vi.contains(interval)) {
					errors.add(o);
				}
			} else {
				errors.add(o);
			}
		}
		if (errors.size() > 0) {
			throw new IntervalException("Interval: " + interval + " conflict with these objects", interval, errors);
		}
		
	}
}
